package com.zhangmeng.tools.editors.ace;


import com.zhangmeng.tools.utils.AlertUtils;
import com.zhangmeng.tools.utils.ClipboardUtils;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import netscape.javascript.JSObject;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author zhangmeng
 * @date 2023年10月7日09:12:41
 * @version 1.0
 * js 编辑器 ace 实现
 */
@Slf4j
public class AceEditor extends AnchorPane {

    private final WebView webView = new WebView();
    private WebEngine engine;
    private AceMode mode = AceMode.JAVA;
    private JSObject window;
    private JSObject editor;
    private File file;

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
    }

    public WebView getWebView() {
        return webView;
    }

    public AceEditor() {
        initialize();
    }

    public AceEditor(File file) {
        String file_content = null;
        try {
            file_content = Files.readString(file.toPath(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            try {
                file_content = Files.readString(file.toPath(), Charset.forName("GBK"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        this.file = file;
        setText(file_content);
        initialize();
    }

    public AceEditor(File file,AceMode mode,AceTheme theme) {
        String file_content = null;
        try {
            file_content = Files.readString(file.toPath(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            try {
                file_content = Files.readString(file.toPath(), Charset.forName("GBK"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        this.file = file;
        if (theme != null){
            this.theme.setValue(theme);
        }

        this.mode = mode;
        setText(file_content);
        initialize();
    }

    public AceEditor(String text) {
        setText(text);
        initialize();
    }

    public void setText(String text) {
        this.text.set(text);
    }

    /**
     * 设置内容
     *
     * @param value
     */
    public void setAceText(String value) {
        editor.call("setValue", value, 1);
        // 或者调用js中定义好的方法
        // JSObject editor = (JSObject) engine.executeScript("window");
        // editor.call("setEditorValue", value);
    }

    private final StringProperty text = new SimpleStringProperty("");

    public StringProperty textProperty() {
        return text;
    }

    public String getText() {
        return text.get();
    }

    /**
     * 修改主题
     */
    public void setAceTheme(AceTheme aceTheme) {
        editor.call("setTheme", aceTheme.getValue());
    }

    /**
     * 主题
     */
    private final ObjectProperty<AceTheme> theme = new SimpleObjectProperty<>(AceTheme.CHROME);

    public ObjectProperty<AceTheme> themeProperty() {
        return theme;
    }

    public AceTheme getTheme() {
        return theme.get();
    }

    /**
     * 设置模式
     */
    public void setAceMode(AceMode mode) {
        window.call("setEditorMode", mode.getValue());
    }

    /**
     * 编辑器是否只读
     *
     * @param val
     */
    private void setAceReadOnly(boolean val) {
        //editor.call("setReadOnly", !val);
        setAceOption("readOnly", !val);
    }

    /**
     * 设置ace.js配置项
     *
     * @param key
     * @param value
     */
    private void setAceOption(String key, Object value) {
        editor.call("setOption", key, value);
    }

    /**
     * 是否可编辑
     */
    private final BooleanProperty editable = new SimpleBooleanProperty(true); // 可编辑

    public boolean isEditable() {
        return editable.get();
    }

    /**
     * 获取选定文本
     *
     * @return
     */
    public String selectedText() {
        // 一下两个方法都可以获取到选中的文本
        // String selectedText = (String) engine.executeScript("window.getSelection().toString()"); // 有些问题
        return (String) editor.call("getSelectedText");
    }

    /**
     * 删除选中的内容
     */
    private void removeSelectText() {
        window.call("removeSelectRange"); // 或者：editor.call("remove", "right");
    }

    private final BooleanProperty showGutter = new SimpleBooleanProperty(true);

    public void setShowGutter(boolean showGutter) {
        this.showGutter.set(showGutter);
    }

    public boolean isShowGutter() {
        return showGutter.get();
    }

    /**
     * 监听值变化，这个由js触发调用。
     *
     * @param text
     */
    public void textChange(String text) {
        //log.info(text);
        setText(text);
    }


    /**
     * 是否显示左侧内容（行号，折叠符号区域）
     *
     * @param val
     */
    private void setAceShowGutter(boolean val) {
        setAceOption("showGutter", val);
    }

    public void initialize() {
        getChildren().addAll(webView);
        webView.setContextMenuEnabled(false);
        engine = webView.getEngine();
        // 界面加载完成执行的方法
        engine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
            if (newState == Worker.State.SUCCEEDED) {
                //
                window = (JSObject) engine.executeScript("window");
                window.setMember("AceEditor", this);
                editor = (JSObject) engine.executeScript("window.editor");
                setAceText(getText());
                setAceTheme(getTheme());
                setAceMode(mode);
                setAceReadOnly(isEditable());
                // WebView 整合 Ace.js 后 Ctrl+C 和 Ctrl+X 有问题，重新处理下。
                webView.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
                    if (event.isControlDown() && event.getCode() == KeyCode.C) {
                        event.consume();
                        ClipboardUtils.setString(selectedText());
                    }

                    if (event.isControlDown() && event.getCode() == KeyCode.X) {
                        event.consume();
                        ClipboardUtils.setString(selectedText());
                        if (isEditable()) {
                            removeSelectText(); // 删除选中的内容
                        }
                    }

                    if (event.isControlDown() && event.getCode() == KeyCode.S){//保存
                        event.consume();
                        if (file != null){
                            try {
                                FileWriter writer = new FileWriter(file);
                                writer.write(getText());
                                writer.flush();
                                writer.close();
                            } catch (IOException ex) {
                                ex.printStackTrace();
                            }
                        }
                        AlertUtils.alert_warning("保存成功!");
                    }

                    if (event.isControlDown() && event.getCode() == KeyCode.O){
                        event.consume();
                        //打开文件
                        Stage stage = new Stage();
                        FileChooser dc = new FileChooser();
                        dc.setTitle("文件选择");
                        dc.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("文件类型", "*.java", "*.go", "*.txt", "*.json", "*.sql"));
                        File file = dc.showOpenDialog(stage);
                        String file_content = null;
                        try {
                            file_content = Files.readString(file.toPath(), StandardCharsets.UTF_8);
                        } catch (IOException e) {
                            try {
                                file_content = Files.readString(file.toPath(), Charset.forName("GBK"));
                            } catch (IOException ex) {
                                ex.printStackTrace();
                            }
                        }
                        this.file = file;
                        setText(file_content);
                        setAceText(getText());
                    }

                });
                // 主题监听
                themeProperty().addListener((observable, oldValue, newValue) -> setAceTheme(newValue));
                // 是否可编辑
                editable.addListener((observable, oldValue, newValue) -> setAceReadOnly(newValue));
                showGutter.addListener((observable, oldValue, newValue) -> setAceShowGutter(newValue));
            }
        });
        engine.setJavaScriptEnabled(true);
        // message print
        engine.getLoadWorker().messageProperty().addListener((observable, oldValue, newValue) -> System.out.printf("engine message : %s%n", newValue));
        // exception print
        engine.getLoadWorker().exceptionProperty().addListener((observable, oldValue, newValue) -> newValue.printStackTrace());
        engine.loadContent(AceHelper.getHtml(this.mode,AceTheme.XCODE_JS)); // 加载html资源
        // engine.load(""); // 同一时间多个WebView访问同一个资源时engine.executeScript();会返回undefined。
    }
}
